// Skeletal program for the "Draw a Clock" assignment
// Written by:  Minglun Gong

// Modified by Daniel Cook for CS4751, Assignment 1
// Submitted January 26, 2013
// Implements midpoint circle algorithm, and Bresenham line algorithm (for 8-octants)
// NOTE: hand positions are calculated with sine/cosine. I was unable to arrive at an implementation that worked correctly with integer arithmetic alone. Line/circle algorithms *do* use integer math only however.

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;

// Main class
public class DrawClock extends Frame {
	GridCanvas grid;
	Label infor;
	Timer timer = new Timer();
	// Constructor
	public DrawClock(int span) {
		super("Analog Clock");
		// create & add a grid canvas
		grid = new GridCanvas(span);
		add("Center", grid);
		// add an information bar
		infor = new Label();
		infor.setAlignment(infor.CENTER);
		add("South", infor);
		addWindowListener(new ExitListener());
		timer.scheduleAtFixedRate((new UpdateClock()), 0, 1000);
	}
	// Exit listener
	class ExitListener extends WindowAdapter {
		public void windowClosing(WindowEvent e) {
			timer.cancel();
			System.exit(0);
		}
	}
	// TimerTask for updating the colck
	class UpdateClock extends TimerTask {
		public void run() {
			Calendar time = Calendar.getInstance();
			int hour = time.get(Calendar.HOUR_OF_DAY);
			int minute = time.get(Calendar.MINUTE);
			int second = time.get(Calendar.SECOND);
			// display the current time
			infor.setText("Current time: " + hour + (minute>9?":"+minute:":0"+minute) + (second>9?":"+second:":0"+second));
			grid.setTime(hour, minute, second);
		}
	}

	public static void main(String[] args) {
		int span = 10;
		if ( args.length == 1 )
			span = Integer.parseInt(args[0]);
		DrawClock window = new DrawClock(span);
		window.setSize(400, 450);
		window.setResizable(true);
		window.setVisible(true);
	}
}

// Canvas with grid shown
class GridCanvas extends Canvas {
	// parameter that controls the span of the grid
	int span, width, height, xoff, yoff;
	int hour, minute, second;

	int i; //tmp
	
	// Initialize the grid size;
	public GridCanvas(int s) {
		span = s;
		i = 0;
	}
	public void setTime(int h, int m, int s) {
		hour = h; minute = m; second = s;
		repaint();
	}
	// Draw the grids
	public void drawGrid(Graphics2D g2D) {
		g2D.setColor(Color.lightGray);
		for ( int x=-width ; x<=width ; x++ )
			g2D.draw(new Line2D.Float(new Dot(x, -height).toCoord(), new Dot(x, height).toCoord()));
		for ( int y=-height ; y<=height ; y++ )
			g2D.draw(new Line2D.Float(new Dot(-width, y).toCoord(), new Dot(width, y).toCoord()));
	}

	//Added for assignment
	public void drawCircle(int x0, int y0, int radius, Graphics2D g2D){
		int x = radius;
		int y = 0;
		int err = 1-x;
	
		while(x >= y){
			//Mirror the current point for each octant
			g2D.fill(new Dot(x+x0, y+y0));
			g2D.fill(new Dot(x+x0, -y+y0));
			g2D.fill(new Dot(-x+x0, y+y0));
			g2D.fill(new Dot(-x+x0, -y+y0));
			g2D.fill(new Dot(y+y0, x+x0));
			g2D.fill(new Dot(y+y0, -x+x0));
			g2D.fill(new Dot(-y+y0, x+x0));
			g2D.fill(new Dot(-y+y0, -x+x0));
			
			y++;
			if(err < 0){
				err += 2*y+1;
			}
			else{
				x--;
				err += 2*(y-x+1);
			}		
		}	
	}

	//Added for assignment
	public void drawLine(int x0, int y0, int x1, int y1, Graphics2D g2D) {
		int x = x0;
		int y = y0;		
		int dx = x1-x0;
		int dy = y1-y0;

		int rx1 = 0;
		int ry1 = 0;
		int rx2 = 0;
		int ry2 = 0;

		if(dx < 0){ //Line runs right to left, mirror about y-axis
			rx1 = -1;
			rx2 = -1;
		}
		else{
			rx1 = 1;
			rx2 = 1;
		}

		if(dy < 0) //Line has negative slope, mirror about x-axis
			ry1 = -1;
		else
			ry1 = 1;
			

		//Determine if the lines dx is larger than it's dy
		//Lines "taller" than they are "wide", lie in the first, fourth,
		// fifth, or eighth octant, and must be "transformed" accordingly
		int longestSide = Math.abs(dx);
		int shortestSide = Math.abs(dy);

		if(longestSide <= shortestSide){
			longestSide = Math.abs(dy);
			shortestSide = Math.abs(dx);
			
			if(dy < 0)
				ry2 = -1;
			else
				ry2 = 1;
			
			rx2 = 0;
		}

		int num = longestSide/2;
		for(int i=0; i<=longestSide; i++){
			g2D.fill(new Dot(x, y));

			num += shortestSide;
			if(num >= longestSide){
				num -= longestSide;
				x += rx1;
				y += ry1;
			}
			else{
				x += rx2;
				y += ry2;
			}
		}

	}

	public void paint(Graphics g) {
		Graphics2D g2D = (Graphics2D)g;
		// calculate the number of cells to be shown
		width = (getWidth() / span - 1) / 2;
		height = (getHeight() / span - 1) / 2;
		xoff = getWidth() / 2;
		yoff = getHeight() / 2;
		drawGrid(g2D);

		//Constrain radius to the shortest of the window dimensions
		//This way the clock will always be entirely visible
		int xr, yr;
		if(height <= width){
			xr = height-2;
			yr = height-1;	
		}
		else{
			xr = width-2;
			yr = width-1;
		}
		
		//Calculate hand positions, draw lines accordingly
		int xs = (int)(Math.cos(second*Math.PI/30 - Math.PI/2)*xr);
		int ys = (int)(-1*Math.sin(second*Math.PI/30 - Math.PI/2)*yr);
		int xm = (int)(Math.cos(minute*Math.PI/30 - Math.PI/2)*(xr-(xr/8)));
		int ym = (int)(-1*Math.sin(minute*Math.PI/30 - Math.PI/2)*(yr-(yr/8)));
		int xh = (int)(Math.cos((hour*30+minute/2)*Math.PI/180-Math.PI/2)*(xr-(xr/3)));
		int yh = (int)(-1*Math.sin((hour*30+minute/2)*Math.PI/180-Math.PI/2)*(xr-(xr/3)));

		g2D.setColor(Color.green);
		drawLine(0, 0, xs, ys, g2D);

		g2D.setColor(Color.blue);
		drawLine(0, 0, xm, ym, g2D);

		g2D.setColor(Color.red);
		drawLine(0, 0, xh, yh, g2D);

		// draw circle
		g2D.setColor(Color.black);
		drawCircle(0, 0, yr, g2D);
	}

	// Represent a dot at given coordinate
	class Dot extends Ellipse2D.Float {
		public Dot(int x, int y) {
			super(x*span-span/2+xoff, -y*span-span/2+yoff, span, span);
		}
		public Point toCoord() {
			return new Point((int)x+span/2, (int)y+span/2);
		}
	}
}
